/*
 * FixedSizeRegisterSequentialFile.cpp
 *
 *  Created on: Oct 17, 2012
 *      Author: m
 */

#include "FixedSizeRegisterSequentialFile.h"

namespace physical
{

const openmode FixedSizeRegisterSequentialFile::defaulOpenmode = std::ios_base::in | std::ios_base::out | std::ios_base::app | std::ios_base::binary;

FixedSizeRegisterSequentialFile::FixedSizeRegisterSequentialFile(
        size_t registerSize, const char filename[], openmode mode,
        size_t readBufferSize, size_t writeBufferSize ) :
        m_registerSource(),
        m_registerSize( registerSize ),
        m_readBufferSize( readBufferSize * m_registerSize ),
        m_writeBufferSize( writeBufferSize * m_registerSize ),
        m_readBuffer(), m_writeBuffer(),
        m_currentReadBufferPosition( NULL ),
        m_currentWriteBufferPosition( NULL ),
        m_readBufferRegisterCount( 0 ),
        m_writeBufferRegisterCount( 0 )
{
#ifndef NDEBUG

    lecturas = 0;
    escrituras = 0;

#endif

    m_registerSource.exceptions( std::fstream::failbit | std::fstream::badbit );

    if ( filename != NULL && filename[0] != '\0' )
    {
        this->open( filename, mode );
    }
}

FixedSizeRegisterSequentialFile::~FixedSizeRegisterSequentialFile()
{
    this->close();
}

void FixedSizeRegisterSequentialFile::initializeBuffers()
{
    m_readBuffer.reset( new byte[m_readBufferSize] );
    m_currentReadBufferPosition = m_readBuffer.get();
    m_readBufferRegisterCount = 0;
    m_writeBuffer.reset( new byte[m_writeBufferSize] );
    m_currentWriteBufferPosition = m_writeBuffer.get();
    m_writeBufferRegisterCount = 0;
}

size_t FixedSizeRegisterSequentialFile::readRegister( byte* registerBuffer )
{
    if ( m_writeBufferRegisterCount > 0 )
        this->flushWriteBuffer();

    if ( m_readBufferRegisterCount == 0 )
        this->loadReadBuffer();

    if ( this->readFromBuffer( registerBuffer ) )
        return m_registerSize;
    else
        return 0;
}

// Método Privado: Se asume que si el buffer de lectura no tiene registros disponibles,
//  entonces ya no hay registros para leer.
bool FixedSizeRegisterSequentialFile::readFromBuffer( byte* registerBuffer )
{
    if ( m_readBufferRegisterCount == 0 )
        return false;

    memcpy( registerBuffer, m_currentReadBufferPosition, m_registerSize );

    m_currentReadBufferPosition += m_registerSize;
    assert( m_currentReadBufferPosition <= m_readBuffer.get() + m_readBufferSize );

    m_readBufferRegisterCount--;
    assert( m_readBufferRegisterCount < ( m_readBufferSize / m_registerSize ) );

    return true;
}

void FixedSizeRegisterSequentialFile::loadReadBuffer()
{
    size_t bytesRead;

    try
    {
        m_registerSource.read( m_readBuffer.get(), m_readBufferSize );
        bytesRead = m_registerSource.gcount();
    }
    catch ( std::fstream::failure &e )
    {
        bytesRead = m_registerSource.gcount();

        if ( ! ( bytesRead > 0 && m_registerSource.eof() ) )
            throw e;
    }

    assert( bytesRead > 0 );
    assert( ( bytesRead % m_registerSize ) == 0 );

    m_currentReadBufferPosition = m_readBuffer.get();
    m_readBufferRegisterCount = bytesRead / m_registerSize;
    assert( m_readBufferRegisterCount > 0 );

#ifndef NDEBUG
    lecturas++;
#endif

}

void FixedSizeRegisterSequentialFile::writeRegister(
        const byteStream newRegister )
{
    if ( this->writeBufferIsFull() )
        this->flushWriteBuffer();

    this->writeToBuffer( newRegister );
}

// Método Privado: Se asume que el buffer de escritura siempre tiene lugar!
void FixedSizeRegisterSequentialFile::writeToBuffer(
        const byteStream registerBuffer )
{
    assert( m_currentWriteBufferPosition <= ( m_writeBuffer.get() + m_writeBufferSize - m_registerSize ) );

    memcpy( m_currentWriteBufferPosition, registerBuffer, m_registerSize );

    m_currentWriteBufferPosition += m_registerSize;

    assert( m_currentWriteBufferPosition <= ( m_writeBuffer.get() + m_writeBufferSize ) );

    m_writeBufferRegisterCount++;

    assert( m_writeBufferRegisterCount <= m_writeBufferSize/m_registerSize );
}

void FixedSizeRegisterSequentialFile::flushWriteBuffer()
{
    std::streampos currentPos;

    if ( m_writeBufferRegisterCount > 0 )
    {
        // save initial position
        currentPos = m_registerSource.tellg();

        // Manually append written data at the end
        m_registerSource.clear();
        m_registerSource.seekg( 0, std::ios::end );

        m_registerSource.write( m_writeBuffer.get(),
                m_writeBufferRegisterCount * m_registerSize );

        // Reset Write Buffer status
        m_currentWriteBufferPosition = m_writeBuffer.get();
        m_writeBufferRegisterCount = 0;

        // Restore position
        m_registerSource.clear();
        m_registerSource.seekg( currentPos, std::ios::beg );

#ifndef NDEBUG
        escrituras++;
#endif
    }
}

size_t FixedSizeRegisterSequentialFile::getRegisterSize()
{
    return m_registerSize;
}

bool FixedSizeRegisterSequentialFile::eof()
{
    bool eofReached;
    std::streampos currentPos;
    std::streampos finalPos;

    if ( m_registerSource.eof() )
    {
        eofReached = true;
    }
    else
    {
        // manually check if eof reached

        // save initial position
        currentPos = m_registerSource.tellg();

        // get finalPos and compare to currentPos
        m_registerSource.clear();
        m_registerSource.seekg( 0, std::ios::end );
        finalPos = m_registerSource.tellg();

        eofReached = currentPos == finalPos;

        // restore initial position
        m_registerSource.clear();
        m_registerSource.seekg( currentPos, std::ios::beg );
    }

    return eofReached && m_readBufferRegisterCount == 0;
}

void FixedSizeRegisterSequentialFile::open( const char filename[],
        openmode mode )
{
    openmode finalOpenMode = mode | std::ios_base::binary;

    m_registerSource.open( filename, finalOpenMode );
    this->initializeBuffers();
}

void FixedSizeRegisterSequentialFile::close()
{
    if ( !m_registerSource.is_open() )
        return;

    this->flushWriteBuffer();
    m_registerSource.close();
}

bool FixedSizeRegisterSequentialFile::writeBufferIsFull()
{
    return m_writeBufferRegisterCount == m_writeBufferSize / m_registerSize;
}

}
